cyclonedx-cli
cyclonedx-cli copied to clipboard
Add support for dependency graph visualization
When working with dependencies, it's important to understand how they're introduced. Since CycloneDX 1.2, dependency graphs are part of the core spec. For previous spec versions, there is a dependency graph extension.
The graph as included in CycloneDX BOMs is, while simple and minimalistic, hard to parse for humans. cyclonedx-cli should include a command that visualizes the graph in some sort of tree structure.
For example, executing for mvn dependency:tree
for Alpine produces the following output:
us.springett:alpine:jar:1.8.0-SNAPSHOT
+- commons-io:commons-io:jar:2.6:compile
+- org.apache.commons:commons-lang3:jar:3.10:compile
+- org.apache.commons:commons-collections4:jar:4.4:compile
+- org.glassfish.jersey.core:jersey-client:jar:2.29.1:compile
| +- jakarta.ws.rs:jakarta.ws.rs-api:jar:2.1.6:compile
| +- org.glassfish.jersey.core:jersey-common:jar:2.29.1:compile
| | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
| | +- org.glassfish.hk2:osgi-resource-locator:jar:1.0.3:compile
| | \- com.sun.activation:jakarta.activation:jar:1.2.1:compile
| \- org.glassfish.hk2.external:jakarta.inject:jar:2.6.1:compile
+- javax.servlet:javax.servlet-api:jar:4.0.1:provided
+- org.glassfish.jersey.containers:jersey-container-servlet:jar:2.29.1:compile
| +- org.glassfish.jersey.containers:jersey-container-servlet-core:jar:2.29.1:compile
| \- org.glassfish.jersey.core:jersey-server:jar:2.29.1:compile
| +- org.glassfish.jersey.media:jersey-media-jaxb:jar:2.29.1:compile
| \- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.2:compile
| \- jakarta.activation:jakarta.activation-api:jar:1.2.1:compile
...
The main focus should be on terminal output. For the future, it may also be helpful to transpile CDX's dependency graph into the DOT language, which would allow generation of graph images with GraphViz.
Thanks for the well detailed issue!
My current plan for sub-commands like this, that produce terminal output, is to have an optional output-format
parameter. This will default to text
which is basic terminal output. But it will also support other machine readable formats.
I think JSON probably makes sense as the first format to support. It's easy to implement and aligns with one of the existing CycloneDX SBOM formats.
@nscuro @stevespringett do you have any opinions on what the first machine readable format should be? This is specifically to support automation scenarios.
Good to see that you have automatability in mind from the very beginning. I think JSON is a great choice.
Would output be able to highlight when there are more than one version of the same component (eg, commons-codec).
In my maven projects I have seen that the shaded jar only includes one version and it's almost always the most recent one... but not always. Thus, there is a need to identify dependency version clashes so that they can be resolved.
Also, would it be possible to identify which components are optional and which required?
If these seem like worthwhile things to do, but not for an MVP, then I can log new issues to cover them.
@msymons The CLI doesn't know what Maven is. Every dependency management tool has a slightly different algorithm for resolving component versions. Maven uses a nearest neighbor method. The tree support in the CLI will only visualize the dependency graph from a provided BOM, so it's not going to know any of the low-level dependency management details. You'll have to use ecosystem specific tools for that.
However, I think identifying component scope (required, optional, excluded) would be a good enhancement.
I understand that the CLI does not know what Maven is. Or npm, etc It only cares about the BOM. However, a BOM can still end up containing multiple versions of the same component. eg:
pkg:maven/org.apache.httpcomponents/[email protected]?type=jar
pkg:maven/org.apache.httpcomponents/[email protected]?type=jar
Dependency resolution in the build has meant that only one of these versions is actually valid (used). But both end up in the BOM. As it happens, one version has a vulnerability (CVE-2020-13956) and one (the newer) does not. Furthermore, tools such OSS Index are not yet reporting the vulnerability. This means that vulnerability alerting in (say) Dependency-Track would not help highlight that an older version of a component is still trying to sneak in.
Hence, all I am seeking is a means of quickly spotting (visualizing) when such dependency version clashes have occurred. Because spotting them is half the battle. Fixing such a clash is pretty easy once they are spotted.
I have BOMs with over 3000 lines (300 components) and manually checking for clashes is a pain.
I see this functionality as being useful for when someone is double checking output from an earlier stage in a DevSecOps pipeline and the someone (me, in this case) is not a developer or might not have access to build logs, etc.
Hi @msymons I have been thinking about some way to query, and modify, the SBOM using this tool. It could help with issue #14 to be able to modify components.
Checking on this enhancement request. At the moment, it doesn't appears as though cyclonedx-cli's 'add' command will generate dependency graph information in a resulting sbom file. (Perhaps I'm overlooking something?)
Is the inclusion of dependency graph information still a forthcoming enhancement?
I wrote a CycloneDX SBOM viewer using HTMl, JavaScript, Bootstrap and DataTables. The viewer also tries to visualize the dependencies section in a HTML unorderered list, for similar reasons already outlined above. Here's how that looks like:
As my code sometimes fails to create the dependency tree recursively (RangeError: Maximum call stack size exceeded), I searched for existing implementations of visualizing the dependencies section of a CycloneDX SBOM, and found this issue.
Is there a visualization feature meanwhile in the CLI or any other related tool?
@sgustafsson You're likely running into this due to circular dependency relationships. When recursing, maintain a list or stack of BOM refs you encountered. For each recursion step, check if you already saw the BOM ref at hand. Terminate the recursion if you did.
This has not yet been added to the CLI, but https://dependencytrack.org/ has a visualization that can handle circular dependencies. It operates on its own data model though, not strictly CycloneDX BOMs.
That is my suspicion too. It happens only in case of "big" dependencies section of SBOMS for npm packages, like https://github.com/CycloneDX/cyclonedx-node-npm/blob/main/demo/juice-shop/example-results/flat/bom.1.5.json. I'll try to tweak my code, or tell GH CoPilot to do it for me :smirk:
Just in case someone finds this issue and also wants to visualize dependencies, here's what I use now:
- For visualizing a tree based on the dependencies: https://github.com/square/dependentree
- For listing all cycles: https://www.npmjs.com/package/graph-cycles
graph-cycles example code here:
import fs from 'fs';
import { analyzeGraph } from 'graph-cycles'
let cyclonedxbom = JSON.parse(fs.readFileSync('bom.json', 'utf8'));
let graph = [];
for (let i = 0; i < cyclonedxbom.dependencies.length; i++) {
let dependency = cyclonedxbom.dependencies[i];
let newGraphEntry = [];
newGraphEntry.push(dependency.ref);
let depsArray = [];
if(!dependency.dependsOn) {
depsArray.push("]");
} else {
for (let j = 0; j < dependency.dependsOn.length; j++) {
let subDependency = dependency.dependsOn[j];
depsArray.push(subDependency);
}
}
newGraphEntry.push(depsArray);
graph.push(newGraphEntry);
}
const analysis = analyzeGraph( graph );
console.log(analysis.cycles);