The Snyk CLI is a great tool used to scan a project and report vulnerabilities discovered in it. The Snyk CLI supports a wide variety of languages and build systems, making it ideal as a generic, go-to solution for vulnerability reporting. However, it only outputs vulnerabilities discovered – it does not generate an SBOM, which is really important for continuous vulnerability scanning, compliance purposes (for example, see the May 12, 2021 Executive Order on Improving the Nation’s Cybersecurity), and is increasingly becoming an expectation for purchasers/users of products. Therefore, I set to work adding support for SBOM creation to Snyk. Spoiler alert: I don’t seem to have completely succeeded (yet?).
How Does Snyk Compare to the Competition?
Snyk offers a really nice developer security platform. The Snyk CLI is easy to integrate directly into development tools, workflows, and automation pipelines, providing vulnerability reports with minimal hassle. Snyk supports many languages and package managers (Gradle, Maven, NPM, Yarn, Go, .NET, etc) so it probably Just Works with your project. Snyk is really close to an ideal solution – it’s just missing that SBOM creation feature to enable conveyance of components to users, continuous vulnerability monitoring, and compliance.
Snyk does offer Snyk Monitor which can continuously monitor a project for vulnerabilities, but it’s not in the standard SBOM format nor can it be shared with all users of the product, so it doesn’t provide the same benefits as an SBOM.
Before going down the rabbit hole of adding a feature to Snyk, it’s worthwhile to compare Snyk to some other options. Here’s a table comparing Snyk to some other products/projects:
Language / Package Manger (file) | Snyk | GitLab Dependency Scanning (formerly Gemnasium) | @appthreat/cdxgen | Syft | Trivy | Nexus IQ Server |
.NET/NuGet (packages.lock.json) | Yes | Yes | Yes | Yes | Yes | No |
Ruby/Gem (Gemfile.lock) | Yes | Yes | Yes | Yes | Yes | Yes |
PHP/composer (composer.lock) | Yes | Yes | Yes | Yes | Yes | Yes |
Python/pip (Pipfile.lock) | Yes | Yes | Yes | Yes | Yes | Yes |
Python/pip (requirements.txt) | Yes | Yes | Yes (but doesn’t include transitive dependencies) | No | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) |
Python/poetry (poetry.lock) | Yes | Yes | Yes | Yes | Yes | Yes |
Node/yarn (yarn.lock) v1 | Yes | Yes | Yes | Yes | Yes | Yes |
Node/yarn (yarn.lock) v2 | Yes | Yes | Yes | Unknown | Maybe? | Yes |
Node/yarn (yarn.lock) v3 | Maybe? Documented no, reality maybe, issues | Yes | Yes | Unknown | Unknown | No |
Node/npm (package-lock.json) v1 | Yes | Yes | Yes | Yes | Unknown | Yes |
Node/npm (package-lock.json) v2 | Yes | Yes | Yes | Unknown | Yes | Yes |
Node/npm (package-lock.json) v3 | Unknown | Yes | Unknown | Unknown | Unknown | Unknown |
Java/Maven (pom.xml) | Yes (supports mvnw) | Yes | Yes (supports mvnw) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) |
Java/Gradle (build.gradle) | Yes (supports gradlew) | Yes | Yes (supports gradlew) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) |
Java/Gradle (build.gradle.kts) | Yes (supports gradlew) | Yes | Yes (supports gradlew) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) | Yes (but doesn’t include transitive dependencies) |
Go/go (go.sum) | Yes | Yes | Yes | Yes | Yes | Yes |
Rust/cargo (cargo.lock) | No | Yes | Yes | Yes | Yes | Yes |
C++/Conan (Conan.lock) | No | No | Yes | Yes | Yes | No |
Another family of options not covered in the table is package manager plugins that generate SBOMs specific to that package manager. For example, @cyclonedx/cyclonedx-npm can be added to NPM projects. Another example is the CycloneDX Gradle Plugin that can be added to Gradle projects. The downside of these tools is that they require changes to the project. And such tools don’t exist for all build systems (for example, I don’t believe that Yarn has such a plugin available yet).
SBOMs are only useful if they are accurate; an SBOM that is missing components/dependencies may actually be worse than not having an SBOM at all for it can give a false sense of security. Therefore, it’s very important that the SBOM generation tool supports the language and package manager of the project and includes all dependencies (including transitive ones). If there isn’t one tool that supports everything, use multiple tools then merge the multiple SBOMs into one.
As this table shows, Snyk’s support is quite good.
What Snyk Can Do Today
The snyk test command outputs a nice report to the terminal. It can also output a SARIF 2.1 formatted file which can be provided to security tools such as GitHub Code Scanning and Fortify SSC.
Here’s the output of running snyk test on my jumpstart example project that uses the Gradle and Yarn v1 package managers:
candrews@craigworking ~/projects/jumpstart$ npx snyk test --all-projects --maven-aggregate-project --fail-fast --configuration-matching="^(runtime.*|default|implementation)"
Testing /home/candrews/projects/jumpstart...
Tested 97 dependencies for known issues, found 5 issues, 9 vulnerable paths.
Issues to fix by upgrading:
Upgrade org.springframework.boot:spring-boot-starter-actuator@2.7.4 to org.springframework.boot:spring-boot-starter-actuator@2.7.5 to fix
✗ Denial of Service (DoS) (new) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426] in com.fasterxml.jackson.core:jackson-databind@2.13.4
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-actuator-autoconfigure@2.7.4 > com.fasterxml.jackson.core:jackson-databind@2.13.4 and 4 other path(s)
Upgrade org.springframework.boot:spring-boot-starter-web@2.7.4 to org.springframework.boot:spring-boot-starter-web@2.7.5 to fix
✗ Denial of Service (DoS) (new) [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426] in com.fasterxml.jackson.core:jackson-databind@2.13.4
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-actuator-autoconfigure@2.7.4 > com.fasterxml.jackson.core:jackson-databind@2.13.4 and 4 other path(s)
Issues with no direct upgrade or patch:
✗ Denial of Service (DoS) [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-2806360] in org.yaml:snakeyaml@1.30
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-starter@2.7.4 > org.yaml:snakeyaml@1.30
This issue was fixed in versions: 1.31
✗ Stack-based Buffer Overflow [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016888] in org.yaml:snakeyaml@1.30
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-starter@2.7.4 > org.yaml:snakeyaml@1.30
This issue was fixed in versions: 1.32
✗ Stack-based Buffer Overflow [Low Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016889] in org.yaml:snakeyaml@1.30
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-starter@2.7.4 > org.yaml:snakeyaml@1.30
This issue was fixed in versions: 1.31
✗ Stack-based Buffer Overflow [Medium Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGYAML-3016891] in org.yaml:snakeyaml@1.30
introduced by org.springframework.boot:spring-boot-starter-actuator@2.7.4 > org.springframework.boot:spring-boot-starter@2.7.4 > org.yaml:snakeyaml@1.30
This issue was fixed in versions: 1.31
Organization: candrews
Package manager: gradle
Target file: build.gradle
Project name: jumpstart
Open source: no
Project path: /home/candrews/projects/jumpstart
Licenses: enabled
-------------------------------------------------------
Testing /home/candrews/projects/jumpstart...
Organization: candrews
Package manager: yarn
Target file: frontend/yarn.lock
Project name: jumpstart
Open source: no
Project path: /home/candrews/projects/jumpstart
Licenses: enabled
✔ Tested 107 dependencies for known issues, no vulnerable paths found.
Next steps:
- Run `snyk monitor` to be notified about new related vulnerabilities.
- Run `snyk test` as part of your CI/test.
Tested 2 projects, 1 contained vulnerable paths.
My Pull Request Adding SBOM Creation to Snyk
snyk test
accepts an argument named --print-deps
that can print the list of dependencies it found in the project being scanned. Therefore, we know the information necessary to produce an SBOM is available, it’s just a matter of massaging it into the right data structures.
Snyk CLI is a Typescript project currently using Typescript 3.9 which is end of life having since its last release in July 2020. I decided to do some yak shaving; I submitted a pull requesting upgrade Snyk CLI to Typescript 4.8. However, Snyk has been very slow to respond so I don’t know if they will accept the pull request or not. Snyk itself stresses the importance of keeping dependencies up to date, so I’m finding it a bit hypocritical that they’re so far behind on this important dependency as well as so many others.
I chose @cyclonedx/cyclonedx-library as the mechanism for creating the SBOM. It offers a clear API, it’s from OWASP (a highly reputable organization), and it’s well supported / actively developed.
Next it was a matter of studying the Snyk CLI source code. Problems I have to solve included:
- How to add a new command. I wanted to create a new command,
snyk client-sbom
. - How to update the documentation. Without documentation, how would anyone know about this new feature?
- How to initiate a scan and get the dependencies.
- How to convert the dependency tree into the @cyclonedx/cyclonedx-library data structures.
- How to write unit/integration tests for this functionality.
The result is feat: add “sbom” command that produces a CycloneDX 1.4 JSON SBOM.
I’ve also published a package to npm, @candrewsintegralblue/snyk, allowing people to use this functionality before Snyk merges the pull request (assuming they ever do). To try it out, from within a project, run:
npx @candrewsintegralblue/snyk client-sbom --all-projects --maven-aggregate-project --fail-fast --configuration-matching="^(runtime.*|default|implementation)" --cyclonedx-json-file-output="sbom.cdx.json"
Snyk’s Response
I’ve been trying to convince Snyk to merge this pull requests… but so far there’s been no (public) response. Talking to Snyk support, it sounds like they want to implement SBOM creation in a different way.
My pull request implements SBOM creation offline, without the need for a Snyk account and without sending any information to Snyk. Perhaps Snyk sees that as a downside? I get the impression that they want to ensure that every use of Snyk CLI requires authentication to the Snyk service, whether that’s necessary or not.
Snyk seems to plan using their existing monitor approach to generate the SBOM. I imagine that the user workflow will be to run snyk monitor
which will create a project in the Snyk service. Then, use a new CLI command or the Snyk web site to download an SBOM created by the Snyk service.
I have some concerns with Snyk’s idea of using the Snyk server to generate the SBOM:
- Some projects have compliance requirements that preclude providing project information to external entities. For example, the government, financial, and health sectors tend to not want to send the list of project dependencies to Snyk. The offline approach doesn’t send this information to Snyk.
- The offline SBOM creation approach implemented in my pull request is inherently faster and more reliable than online SBOM creation since it doesn’t require any network communication nor does it require a service to be available.
- The SBOM can contain component (dependency) integrity hashes, conveying exactly what dependency files were used. My pull request doesn’t currently add that information to the SBOM, but the information is available so it could so. The Snyk service doesn’t know exactly what dependency files were used so it cannot include this information in SBOMs it generates. For dependencies it can access (public dependencies from maven, for example), it could include the hashes of the files that should have been used; for private dependencies, it couldn’t even do that. However, “should have been used” hashes are worse than no hashes as all, since they’re not reality and can therefore be misleading resulting in a false sense of security. Integrity hashes can be used to ensure no man in the middle supply chain vulnerabilities exist.
I’m keeping my fingers crossed hoping that Snyk will accept my pull request or at least implement the same offline SBOM generation functionality in their own way.
Creating SBOMs with the Snyk CLI by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
I appreciate the work you’ve done here, even if Snyk doesn’t. We need tools for SBOM in a big way… the more the merrier.
Time for a fork??
Great writeup here! GitLab’s Dependency Scanning creates CycloneDX-formatted SBOMs by default out of the box. See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#cyclonedx-software-bill-of-materials.
Now, 6 months later, there is indeed the possibility to generate an SBOM using the Snyk CLI. But as you predicted it requires an internet connection and an at least in the current state also an enterprise plan:
https://docs.snyk.io/snyk-cli/commands/sbom
That is truly disappointing.
It appears that Snyk took that approach purely as a money grab – it’s not the right approach for their customers and eliminates the possibility to use Snyk in many situations (including air-grapped or other security sensitive environments).
Since Snyk now has an “sbom” subcommand, I’ve updated my
@candrewsintegralblue/snyk
fork to rename its client side, offline sbom subcommand to be “client-sbom” so as to not conflict. To generate an sbom client side, run:npx @candrewsintegralblue/snyk client-sbom
See
npx @candrewsintegralblue/snyk --help
for details.