How to generate an SBOM for JavaScript and Node.js applications

SnykSec - Jun 21 '23 - - Dev Community

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.

“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
Enter fullscreen mode Exit fullscreen mode

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:

curl -H "Authorization: token $SNYK_TOKEN" \
"https://api.snyk.io/rest/orgs//projects//sbom?version=2023-03-20&format=cyclonedx1.4%2Bjson"
Enter fullscreen mode Exit fullscreen mode

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:

curl -H "Authorization: token $SNYK_TOKEN" \
"https://api.snyk.io/rest/orgs//projects//sbom?version=2023-03-20&format=cyclonedx+json" | jq .
Enter fullscreen mode Exit fullscreen mode

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:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "version": 1,
  "metadata": {
    "timestamp": "2023-02-21T11:01:07Z",
    "tools": [
      {
        "vendor": "Snyk",
        "name": "Snyk Open Source"
      }
    ],
    "component": {
      "bom-ref": "1-goof@1.0.1",
      "type": "application",
      "name": "goof",
      "version": "1.0.1",
      "purl": "pkg:npm/goof@1.0.1"
    },
    "properties": [
      {
        "name": "snyk:org_id",
        "value": ""
 },
 {
 "name": "snyk:project\_id",
 "value": ""
 }
 ]
 },
 "components": [
 {
 "bom-ref": "2-adm-zip@0.4.7",
 "type": "library",
 "name": "adm-zip",
 "version": "0.4.7",
 "purl": "pkg:npm/adm-zip@0.4.7"
 },
 {
 "bom-ref": "3-bytes@1.0.0",
 "type": "library",
 "name": "bytes",
 "version": "1.0.0",
 "purl": "pkg:npm/bytes@1.0.0"
 },
Enter fullscreen mode Exit fullscreen mode

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:

  {
      "ref": "63-utils-merge@1.0.0"
    },
    {
      "ref": "64-serve-static@1.9.3",
      "dependsOn": [
        "34-escape-html@1.0.1",
        "53-parseurl@1.3.3",
        "62-send@0.12.3",
        "63-utils-merge@1.0.0"
      ]
    },
    {
      "ref": "65-type-is@1.6.16",
      "dependsOn": [
        "6-media-typer@0.3.0",
        "37-mime-types@2.1.23"
      ]
    },
Enter fullscreen mode Exit fullscreen mode

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 and Project 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:

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:

{
  "name": "my-sbom-learning-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "debug": "^4.3.4"
  },
  "dependencies": {
    "fastify": "^4.13.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

snyk sbom --format=cyclonedx1.4+json

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "version": 1,
  "metadata": {
    "timestamp": "2023-02-27T08:06:35Z",
    "tools": [
      {
        "vendor": "Snyk",
        "name": "Snyk Open Source"
      }
    ],
    "component": {
      "bom-ref": "1-a@1.0.0",
      "type": "application",
      "name": "a",
      "version": "1.0.0",
      "purl": "pkg:npm/a@1.0.0"
    }
  },
  "components": [
    {
      "bom-ref": "2-fast-deep-equal@3.1.3",
      "type": "library",
      "name": "fast-deep-equal",
      "version": "3.1.3",
      "purl": "pkg:npm/fast-deep-equal@3.1.3"
    },
    {
      "bom-ref": "3-json-schema-traverse@1.0.0",
      "type": "library",
      "name": "json-schema-traverse",
      "version": "1.0.0",
      "purl": "pkg:npm/json-schema-traverse@1.0.0"
    },
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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:

  1. Does any of these packages carry a license that is not compliant with my organization guidelines and usage?
  2. Are all of these packages actively maintained?
  3. 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= --format=cyclonedx1.4+json [targetDirectory] 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This will result in an output such as the following partial JSON response of our reference Fastify project from earlier:

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "my-sbom-learning-project@1.0.0",
  "documentNamespace": "https://snyk.io/spdx/sbom-",
  "creationInfo": {
    "licenseListVersion": "3.19",
    "creators": [
      "Tool: Snyk Open Source",
      "Organization: Snyk"
    ],
    "created": "2023-05-30T12:09:22Z"
  },
  "packages": [
    {
      "name": "my-sbom-learning-project",
      "SPDXID": "SPDXRef-1-my-sbom-learning-project-1.0.0",
      "versionInfo": "1.0.0",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:npm/my-sbom-learning-project@1.0.0"
        }
      ]
    },
    {
      "name": "fastify",
      "SPDXID": "SPDXRef-2-fastify-4.17.0",
      "versionInfo": "4.17.0",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:npm/fastify@4.17.0"
        }
      ]
    },
    {
      "name": "@fastify/ajv-compiler",
      "SPDXID": "SPDXRef-3-fastify-ajv-compiler-3.5.0",
      "versionInfo": "3.5.0",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:npm/%40fastify/ajv-compiler@3.5.0"
        }
      ]
    },
    {
      "name": "ajv",
      "SPDXID": "SPDXRef-4-ajv-8.12.0",
      "versionInfo": "8.12.0",
      "downloadLocation": "NOASSERTION",
      "copyrightText": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:npm/ajv@8.12.0"
        }
      ]
    },
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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)
  2. 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.
  3. 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.
  4. 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.
  5. Build information: This includes details about how the software was built, such as the build environment, tools used, and the build configuration settings.
  6. 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 CVE-2014-6393 N/A
Lodash 4.17.16 MIT CVE-2021-23337, CVE-2020-28500, CVE-2020-8203 N/A
Vue 2.5.16 MIT CVE-2018-6341 N/A
Axios 0.21.0 MIT CVE-2020-28168 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
  • The Snyk CLI
  • 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .