Lesson 7: Visualize flows¶
The visualize command turns a reachables slice into things you can actually
share: a rich console report, a mermaid.js flowchart as a .mmd file, and a
single-file HTML page that renders it. With a CycloneDX SBOM passed alongside,
it also computes how many SBOM components the analysis proved reachable, which
turns the diagram into an SBOM reachability review.
The console report¶
On the cdxgen reachables from lesson 2, with the SBOM cdxgen generated for
itself (yes, cdxgen analysing cdxgen; run node bin/cdxgen.js -o sbom.cdx.json
--no-referenced in the cdxgen checkout to reproduce it):
$ atom-tools visualize -i cdxgen-reports/cdxgen.reachables.json \
-b cdxgen-reports/sbom.cdx.json -o cdxgen-viz
╭──────────────────────────── Reachables overview ─────────────────────────────╮
│ Flow groups: 789 Files: 9 Sources: 789 Tagged flows: 789 │
│ Reachable packages: 0 │
│ SBOM components: 7813 Proven reachable: 0 (0%) │
╰────────────────────────────────────────────────────────────────────────────╯
Data flows by file
├── lib/stages/postgen/introspection/score.js (191)
│ ├── _tmp_87:984 → substituteVersionPlaceholders(:567 [exported, 47 steps]
│ └── _tmp_87:984 → shapeCommand(resolved.command // '', (:588 [exported, 67 steps]
├── lib/cli/nativeBom.js (189)
│ ├── context:1526 → function createGoBom = async function createGoBom(path,
│ │ options) (:196 [framework-input, 25 steps]
│ └── ...
├── lib/cli/managedBom.js (136)
│ ├── context:1526 → function createPixiBom = function createPixiBom(path,
│ │ options) (:175 [framework-input, 24 steps]
│ └── ...
...
The overview panel is the honest headline: 789 flow groups across 9 files, and, in bold numbers, "SBOM components: 7813, Proven reachable: 0 (0%)".
That zero deserves its own paragraph, because reading it wrong wastes an afternoon. The SBOM join matches the purls carried by flow groups against SBOM components. The cdxgen reachables from lesson 2 carry no purls, because the babel-only frontend run made no dependency boundary resolution, so nothing can match, and the honest rendering of "this analysis could not make that join" is zero proven, not "zero percent of cdxgen's dependencies are used". The contrast case follows below, where a slice that does carry purls proves 8 of 237 SBOM components reachable.
The mermaid diagram¶
The same invocation wrote cdxgen-viz.mmd and cdxgen-viz.html. The mermaid
file is plain text and diffable; the HTML loads mermaid.js from a CDN and
renders the diagram standalone (pass --mermaid-source with a local
mermaid.min.js for offline viewing). Renderers can be selected with -f:
all (default), console, mermaid, html.
The diagram styles sources green, high risk sinks red (sql, ssrf,
code-execution and friends), other tagged sinks orange, and package hexagons
purple carrying the license when an SBOM is given. Flows are grouped into
subgraphs by file and capped with --max-flows, keeping the most interesting
flows first. Labels carry the full relative path, sink expression and purl,
soft-wrapped rather than truncated; --label-wrap 0 puts each on one line.
Here is the cdxgen diagram's opening, verbatim from cdxgen-viz.mmd:
flowchart LR
classDef source fill:#1a7f37,stroke:#116329,color:#fff
classDef sinkHigh fill:#cf222e,stroke:#a40e26,color:#fff
classDef sinkWarn fill:#d29922,stroke:#9a6700,color:#fff
classDef pkg fill:#8250df,stroke:#5c33b8,color:#fff
subgraph sg0["lib/stages/postgen/introspection/score.js"]
n1("_tmp_87:984"):::source
n2["substituteVersionPlaceholders(:567"]:::sinkWarn
n1 -->|"exported"| n2
n3["shapeCommand(resolved.command // '', (:588"]:::sinkWarn
n1 -->|"exported"| n3
end
The purl-rich case: petclinic with an SBOM¶
The committed petclinic fixtures show the full feature set working, because that reachables slice carries purls and the companion SBOM carries licenses. Run from the atom-tools checkout:
$ atom-tools visualize -i test/data/java-petclinic-reachables.json \
-b test/data/java-petclinic-bom.json -o petclinic-viz --max-flows 12
╭──────────────────────────── Reachables overview ─────────────────────────────╮
│ Flow groups: 163 Files: 11 Sources: 78 Tagged flows: 78 │
│ Reachable packages: 8 │
│ SBOM components: 237 Proven reachable: 8 (3%) │
╰────────────────────────────────────────────────────────────────────────────╯
Data flows by file
├── src/main/java/.../owner/OwnerController.java (73)
│ ├── redirectAttributes:78 → redirectAttributes.addFlashAttribute('error',
│ │ 'There was an error in creating the owner.'):80 [framework-input, 3 steps]
│ │ via pkg:maven/org.springframework/spring-web@7.0.8
│ ├── owner:95 → Page ownersResults =
│ │ this.findPaginatedForOwnersLastName(page, lastName):107
│ │ [framework-input, 12 steps]
│ │ via pkg:maven/org.springframework.data/spring-data-commons@4.1.0
...
8 of 237 SBOM components (3%) are proven reachable.
And the diagram it wrote, rendered here directly (this is the actual
petclinic-viz.mmd content, trimmed to fit):
flowchart LR
classDef source fill:#1a7f37,stroke:#116329,color:#fff
classDef sinkHigh fill:#cf222e,stroke:#a40e26,color:#fff
classDef sinkWarn fill:#d29922,stroke:#9a6700,color:#fff
classDef pkg fill:#8250df,stroke:#5c33b8,color:#fff
subgraph sg0["src/main/java/org/springframework/samples/<br/>petclinic/owner/OwnerController.java"]
n1("redirectAttributes:78"):::source
n2["redirectAttributes.addFlashAttribute('error', <br/>'There was an error in creating the owner.'):80"]:::sinkWarn
n1 -->|"framework-input"| n2
n4("owner:95"):::source
n5["Page ownersResults = this.<br/>findPaginatedForOwnersLastName(page, <br/>lastName):107"]:::sinkWarn
n4 -->|"framework-input"| n5
n6("lastName:100"):::source
n6 -->|"pii-full-name"| n5
end
Read the edge labels as the taint reason: framework-input for framework-fed
parameters, pii-full-name when the source carries a person's name. That
pii-full-name edge is the kind of detail a reviewer scans diagrams for: a
form field, tagged as personal data, flowing into the paginated query.
Chunked inputs and reachability review¶
visualize merges chunked reachable slices automatically (reachables.json,
reachables_1.json, ...) and accepts globs and comma-separated lists, so it can
run directly over atom's raw output directory. The package table at the bottom
of the console report lists every reachable package with its license and a
reachability marker when an SBOM was given, which is the artefact to attach to
a dependency review: "these 8 of our 237 components are proven reachable, here
is the flow evidence for each".
Exercises¶
Run visualize on the cdxgen slice without -b and note exactly which numbers
change in the overview panel. Then run it on the juiceshop chunks with the
filter from lesson 5 applied first (-p package filter), and compare which
flows the diagram keeps under --max-flows 20 versus 60.