How to generate an SBOM for JavaScript and Node.js applications
May 22, 2023
0 mins readEditor's note: May 31, 2023
Now that Snyk's sbom
command has made it to general availability, this post has been updated to show how Snyk can be used to generate an SBOM of SPDX specification.
What is SBOM?
SBOM is the acronym for Software Bill of Materials, which is a list of all the open source npm packages that are part of your project. But it’s not only limited to open source or software packages, and can include operating system libraries, microservices inventory and more.
Your npm software packages would be represented across two files — the package.json
which contains your direct dependencies, and a lockfile such as package-lock.json
or yarn.lock
which would represent the entire tree of installed dependencies when you build your JavaScript or Node.js application projects.
Why is it important to maintain an SBOM for a Node.js application?
The JavaScript ecosystem is no stranger to security incidents that impact developers. Recent examples include protestware, a type of malicious malware which targeted the corruption of disk data, and malicious npm packages exploiting dependency confusion attacks to infiltrate internal organizations computer systems.
With today’s ubiquitous adoption of open source libraries and the growing security incidents on software repositories like npm, the ability to assess and pin-point if a specific dependency version is used in your application is essential. Furthermore, these security risks increase the need to act quickly through rolling dependency upgrades or find-and-replace in a short period of time.
“A formal record containing the details and supply chain relationships of various components used in building software” — NIST (National Institute of Standards and Technology)
As developers, you’ll find value in an SBOM as a holistic inventory of the dependencies that comprise your tech stack, allowing you to update outdated and vulnerable dependencies, or replace packages that have no fix or use an incompatible license. Moreover, it allows you to waive off vulnerabilities as non-applicable because of your own specific use-cases. Lastly, you can expect to get asked by your security and operations teams about generating an SBOM for your JavaScript and Node.js applications due to government cybersecurity compliance requirements to ensure you keep track of supply chain security.
Using the Snyk API to generate an SBOM for a Node.js application
Snyk is a free developer-security platform that automates security finding and fixing for developers. Monitoring your projects through Snyk guarantees you get security fixes in time.
Currently, SBOM capabilities are only available on paid customer plans. If you’re part of one, let’s get started with generating an SBOM for a Node.js application.
Pre-requisites:
A Snyk paid account.
Access to the Snyk API
A JavaScript or Node.js project imported to the Snyk platform
The curl command line tool
Step 1: Import a JavaScript project to Snyk
The first step is to import a project to the Snyk platform so all of the npm dependencies and libraries it contains can be scanned, monitored, and analyzed.
Click here for the step-by-step process to import a project (or a quick 1 minute video if you prefer).
Step 2: Get your Snyk token ready
To make an API call to the Snyk platform, we’ll need a Snyk API token that has permissions to query the project’s SBOM reporting endpoint.
If you use the Snyk CLI, one way to find the Snyk API token is to run the following command:
$ snyk config get api
However, this Snyk API token might not be privileged to make requests to the organization or project. Instead, we encourage you to generate a new dedicated service account for this task so that you can automate it from CI and other systems.
To create a service account go to your organization settings, then to service accounts and provide a name for the new service account to be created along with the Org Admin role, and click the Create button.
You will be presented with an API token. Save it somewhere safe and keep it handy, we’ll be using it soon to query the Snyk API to generate an SBOM.
You can find more information in the Snyk user documentation about Service Accounts management.
Step 3: Find the organization and project IDs
In the same settings page, you will find your organization ID. Copy it and keep it handy too:
Next, we’ll get the project ID for the JavaScript project that we imported to Snyk.
Go to Projects and find the project you want to generate an SBOM for, and then click to view the details:
In the project details page click on the Settings tab on the top right, and scroll down to copy the Project ID:
Step 4: Generate the SBOM using curl and the Snyk API
By now, you should have the following available:
A Snyk API token
The organization ID
The project ID
We’re ready to send an API request and get the SBOM report. We’ll use the curl command line tool to send the HTTP request to the Snyk API:
1curl -H "Authorization: token $SNYK_TOKEN" \
2"https://api.snyk.io/rest/orgs/<ORG_ID>/projects/<PROJECT_ID>/sbom?version=2023-03-20&format=cyclonedx1.4%2Bjson"
Results returned by the API are in JSON format and use the CycloneDX standard for the SBOM report. Please note the use of the URL encoding at the end of the API query parameter of %2B
which encodes the character +
.
Tip: You can specify several instances of the format
query parameter to receive multiple format versions of the SBOM.
The output data may be too large or not well formatted for a console output, so we can append a pipe to jq
to make it prettier and readable:
1curl -H "Authorization: token $SNYK_TOKEN" \
2"https://api.snyk.io/rest/orgs/<ORG_ID>/projects/<PROJECT_ID>/sbom?version=2023-03-20&format=cyclonedx+json" | jq .
The generated SBOM report is now readable and should look something like this if you imported a project managed by the JavaScript npm package manager:
1{
2 "bomFormat": "CycloneDX",
3 "specVersion": "1.4",
4 "version": 1,
5 "metadata": {
6 "timestamp": "2023-02-21T11:01:07Z",
7 "tools": [
8 {
9 "vendor": "Snyk",
10 "name": "Snyk Open Source"
11 }
12 ],
13 "component": {
14 "bom-ref": "1-goof@1.0.1",
15 "type": "application",
16 "name": "goof",
17 "version": "1.0.1",
18 "purl": "pkg:npm/goof@1.0.1"
19 },
20 "properties": [
21 {
22 "name": "snyk:org_id",
23 "value": "<REDACTED>"
24 },
25 {
26 "name": "snyk:project_id",
27 "value": "<REDACTED>"
28 }
29 ]
30 },
31 "components": [
32 {
33 "bom-ref": "2-adm-zip@0.4.7",
34 "type": "library",
35 "name": "adm-zip",
36 "version": "0.4.7",
37 "purl": "pkg:npm/adm-zip@0.4.7"
38 },
39 {
40 "bom-ref": "3-bytes@1.0.0",
41 "type": "library",
42 "name": "bytes",
43 "version": "1.0.0",
44 "purl": "pkg:npm/bytes@1.0.0"
45 },
The generated SBOM report is a valid CycloneDX 1.4 document containing the software bill of material for the given Snyk project.
It features rich metadata and will also report about transitive dependencies to highlight dependents libraries. For example, from the same report as above:
1 {
2 "ref": "63-utils-merge@1.0.0"
3 },
4 {
5 "ref": "64-serve-static@1.9.3",
6 "dependsOn": [
7 "34-escape-html@1.0.1",
8 "53-parseurl@1.3.3",
9 "62-send@0.12.3",
10 "63-utils-merge@1.0.0"
11 ]
12 },
13 {
14 "ref": "65-type-is@1.6.16",
15 "dependsOn": [
16 "6-media-typer@0.3.0",
17 "37-mime-types@2.1.23"
18 ]
19 },
The information you can expect to find as part of the CycloneDX output format at this stage is the following:
Direct dependencies
Transitive dependencies and their relationships to the packages they depend on.
Snyk
Org
andProject
identifiers that this SBOM was generated for.The application or project’s name that this SBOM was generated for, if available as part of the package manifest file that was provided as input.
In the API documentation, you can explore more usage options for the Snyk API for SBOM, such as support for serialization in XML instead of JSON or SPDX specification document output as the software bill of material standard.
Now, you have a way to automate SBOM generation for your open source dependencies. What are you going to build next? Share with us on the DevSecOps Discord community.
Using the Snyk CLI to generate an SBOM for a Node.js application
If you don’t have direct access to the Snyk API you can take advantage of the Snyk CLI, which has a wide support for many operating systems and can be used to find, fix, monitor, and report across all of Snyk’s products — from static code analysis to infrastructure as code.
Let’s get started with generating an SBOM for any JavaScript application that’s managed through a package manager.
Pre-requisites:
The Snyk CLI (learn how to install the Snyk CLI)
Snyk CLI version >= 1.1071.0
In a JavaScript project directory where you already have a package.json
manifest file we can analyze it for all the software bill of materials it consists of.
Let’s consider a Node.js project with debug
as a devDependency and fastify
as the web application framework set as the production dependencies:
1{
2 "name": "my-sbom-learning-project",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [],
10 "author": "",
11 "license": "ISC",
12 "devDependencies": {
13 "debug": "^4.3.4"
14 },
15 "dependencies": {
16 "fastify": "^4.13.0"
17 }
18}
So for merely two direct dependencies, what is the transitive dependency cost? This is where generating an SBOM for the project shines in providing a complete dependency view of the project. Let’s run the following snyk
command, which will use the current directory to find for a supported package.json
file and analyze it:
1snyk sbom --format=cyclonedx1.4+json
2
3{
4 "bomFormat": "CycloneDX",
5 "specVersion": "1.4",
6 "version": 1,
7 "metadata": {
8 "timestamp": "2023-02-27T08:06:35Z",
9 "tools": [
10 {
11 "vendor": "Snyk",
12 "name": "Snyk Open Source"
13 }
14 ],
15 "component": {
16 "bom-ref": "1-a@1.0.0",
17 "type": "application",
18 "name": "a",
19 "version": "1.0.0",
20 "purl": "pkg:npm/a@1.0.0"
21 }
22 },
23 "components": [
24 {
25 "bom-ref": "2-fast-deep-equal@3.1.3",
26 "type": "library",
27 "name": "fast-deep-equal",
28 "version": "3.1.3",
29 "purl": "pkg:npm/fast-deep-equal@3.1.3"
30 },
31 {
32 "bom-ref": "3-json-schema-traverse@1.0.0",
33 "type": "library",
34 "name": "json-schema-traverse",
35 "version": "1.0.0",
36 "purl": "pkg:npm/json-schema-traverse@1.0.0"
37 },
The above CycloneDX output is cut at around 40 lines to avoid cluttering the text on this article. The complete software bill of material for such a small direct dependency footprint is 749 lines of formatted JSON.
We can use jq to count how many open source dependencies this project has:
snyk sbom --format=cyclonedx1.4+json | jq ".components | length"
And we get a total of 60 open source packages as the total footprint for the software bill of materials for this project.
The importance of an SBOM would become apparent now — it’s more than just to satisfy compliance or regulation requirements set out by your security team or a government order. By now, it’s clear that your project depends on 60 open source packages and not just the 2 you directly installed. This is the concern of the software supply chain, as its security implications are significant and pose direct risks to consumers.
Finding out your project actually relies on 60 open source packages, you might ask yourself one of the following questions:
Does any of these packages carry a license that is not compliant with my organization guidelines and usage?
Are all of these packages actively maintained?
What happens if a new vulnerability is discovered for one of these 60 packages?
These are the type of project health concerns that we have been working at solving with the help of the Snyk Advisor — providing a health score that takes into account the project’s popularity, maintenance, security, and community metrics.
Back to SBOMs, the following shows the complete SBOM command line usage available to scan projects:
$ snyk sbom --file=<file> --format=cyclonedx1.4+json [targetDirectory]
Generate an SBOM of SPDX specification
The Snyk CLI and API routes also support the SPDX specification. By updating the format
command-line flag to spdx2.3+json
we can generate an SPDX compliant SBOM output in JSON format. Here is how:
$ snyk sbom --format=spdx2.3+json
This will result in an output such as the following partial JSON response of our reference Fastify project from earlier:
1{
2 "spdxVersion": "SPDX-2.3",
3 "dataLicense": "CC0-1.0",
4 "SPDXID": "SPDXRef-DOCUMENT",
5 "name": "my-sbom-learning-project@1.0.0",
6 "documentNamespace": "https://snyk.io/spdx/sbom-",
7 "creationInfo": {
8 "licenseListVersion": "3.19",
9 "creators": [
10 "Tool: Snyk Open Source",
11 "Organization: Snyk"
12 ],
13 "created": "2023-05-30T12:09:22Z"
14 },
15 "packages": [
16 {
17 "name": "my-sbom-learning-project",
18 "SPDXID": "SPDXRef-1-my-sbom-learning-project-1.0.0",
19 "versionInfo": "1.0.0",
20 "downloadLocation": "NOASSERTION",
21 "copyrightText": "NOASSERTION",
22 "externalRefs": [
23 {
24 "referenceCategory": "PACKAGE-MANAGER",
25 "referenceType": "purl",
26 "referenceLocator": "pkg:npm/my-sbom-learning-project@1.0.0"
27 }
28 ]
29 },
30 {
31 "name": "fastify",
32 "SPDXID": "SPDXRef-2-fastify-4.17.0",
33 "versionInfo": "4.17.0",
34 "downloadLocation": "NOASSERTION",
35 "copyrightText": "NOASSERTION",
36 "externalRefs": [
37 {
38 "referenceCategory": "PACKAGE-MANAGER",
39 "referenceType": "purl",
40 "referenceLocator": "pkg:npm/fastify@4.17.0"
41 }
42 ]
43 },
44 {
45 "name": "@fastify/ajv-compiler",
46 "SPDXID": "SPDXRef-3-fastify-ajv-compiler-3.5.0",
47 "versionInfo": "3.5.0",
48 "downloadLocation": "NOASSERTION",
49 "copyrightText": "NOASSERTION",
50 "externalRefs": [
51 {
52 "referenceCategory": "PACKAGE-MANAGER",
53 "referenceType": "purl",
54 "referenceLocator": "pkg:npm/%40fastify/ajv-compiler@3.5.0"
55 }
56 ]
57 },
58 {
59 "name": "ajv",
60 "SPDXID": "SPDXRef-4-ajv-8.12.0",
61 "versionInfo": "8.12.0",
62 "downloadLocation": "NOASSERTION",
63 "copyrightText": "NOASSERTION",
64 "externalRefs": [
65 {
66 "referenceCategory": "PACKAGE-MANAGER",
67 "referenceType": "purl",
68 "referenceLocator": "pkg:npm/ajv@8.12.0"
69 }
70 ]
71 },
What else can you expect from SBOM?
Snyk is committed to helping developers build and ship secure applications through instant insecure code findings at the IDE or automated fix pull requests for software repositories on GitHub or Bitbucket.
In this article, we focused on the dependencies of JavaScript applications as part of the inventory an SBOM contains. However, it exposes various other helpful data points related to the software components used in a project. This extended information can be crucial to understanding not only the composition of a software product, but also managing its security risks, compliance requirements, and licensing. Some examples of data that can be provided with an SBOM include:
Component details: This includes information about each component used in the software, such as the npm package name, its version, and source origin (whether on the npmjs repository or a GitHub repository)
License information: This includes details about the licenses governing each component used in the software, such as its type, terms, and obligations. While you might be used to npm packages licensed under MIT or Apache, a nested dependency might use an incompatible license such as the GNU GPL and expose you to legal risks.
Vulnerability data: This includes information about any publicly known security vulnerabilities associated with the npm packages used in the software, whether direct or indirectly impacting it.
Cryptographic data: This includes information about any cryptographic components used in the software, such as their algorithms and key sizes. For example, if an application makes use of an insecure algorithm such as the insecure MD5 hash, you’ll want to be aware and take the necessary actions.
Build information: This includes details about how the software was built, such as the build environment, tools used, and the build configuration settings.
Metadata: This includes any additional information relevant to the software, such as the project name, version, and author.
To provide a practical example of how an SBOM will be useful for you as a JavaScript developer, consider the following table:
Component Name | Version | License Type | Vulnerability Data | Cryptographic Data |
---|---|---|---|---|
Express | 4.4.0 | MIT | N/A | |
Lodash | 4.17.16 | MIT | N/A | |
Vue | 2.5.16 | MIT | N/A | |
Axios | 0.21.0 | MIT | N/A |
At the time of writing this article, Snyk exposes dependencies and their relationships through the following formats:
CycloneDX version 1.4 in JSON
CycloneDX version 1.4 in XML
SPDX version 2.3 in JSON
The team is working to support other specification sections, such as vulnerabilities, licenses, and more.
Snyk makes it possible to work with SBOMs in other ways too:
The SBOM checker tool which is a free website to post an SBOM artifact and have Snyk find vulnerabilities listed in the npm packages.
The Snyk API section on SBOM
Deep-dive into the SBOM user documentation
The Bomber open source project can read SBOM specification format as input and output a list of vulnerabilities. It may be useful if you need to work with closed source components from vendors, and it also supports Snyk as a provider for the vulnerability data.
If you are a Java developer or you have any Java-based applications developed in your organization, check out my colleague, Brian Vermeer’s, article on how to create SBOMs in Java with Maven and Gradle.
For a more detailed introduction I recommend reading Snyk’s article on software bill of materials (SBOM) for open source supply chain security.
Secure your open source dependencies
Snyk's dev-first tooling provides one-click fix PRs for vulnerable open source dependencies and their transitive dependencies.