A Maven coordinate is groupId:artifactId — com.mycorp:auth-lib
lives in a namespace nobody else can publish into, so an internal artifact name is safe by
construction. PyPI and npm (without a scope) share one flat namespace with the entire public
internet: an unscoped name your build resolves internally, like mycorp-auth, is
simultaneously up for grabs on the public index. If your resolver — or a misconfigured private
registry proxy — ever checks the public index too, an attacker who registers that name first
gets your build to install their package. This is dependency confusion, and an SBOM
is what you check it against.
A CycloneDX document keeps two views of the same dependency tree: components is a
flat list — every package the scanner found, at any depth — and dependencies is the
edge list between their bom-refs, i.e. the actual graph. The scene below is
f1-service's real SBOM: five direct dependencies and one transitive dependency of
requests. Pick a component to see whether its name is a confusion risk, and why.
mycorp-billing never appears in f1-service's own
dependsOn — it is requests's dependency, reached two hops down. A
checker that loops over the flat sbom["components"] list would see the name, but
it cannot answer the two questions a real finding needs: does this component actually ship
(a components list can carry entries nothing reachable from the root pulls in — a
stale scan, a dev-only tool, a second root in a merged BOM), and who pulls it in, which
is the line you have to change to fix it. Only the graph answers both. The natural first draft
walks only the root's direct dependsOn instead of the full reachable set —
the root's own edges are the first thing you read off the SBOM — and that bug stays invisible
until a risky name happens to sit more than one hop down.
-/_/. folded to one
separator) and match a true prefix, never a substring, or you flag public packages that merely
contain the word and miss internal names spelled with different separators. And walk the whole
reachable graph from the root, not just its direct children — CycloneDX's
dependencies array exists precisely so a scanner does not have to guess how deep a
name might be hiding.